import {
  AxiosRequestConfig,
  AxiosPromise,
  Method,
  AxiosResponse,
  ResolveFn,
  RejectFn,
} from "../types";
import dispatchRequest from "./dispatchRequest";
import InterceptorManage from "./interceptorManage";

interface Interceptor {
  request: InterceptorManage<AxiosRequestConfig>;
  response: InterceptorManage<AxiosResponse>;
}

interface PromiseChain<T> {
  resolved: ResolveFn<T> | ((config: AxiosRequestConfig) => AxiosPromise);
  rejected?: RejectFn<T>;
}

export default class Axios {
  public interceptors: Interceptor;
  constructor() {
    this.interceptors = {
      request: new InterceptorManage<AxiosRequestConfig>(),
      response: new InterceptorManage<AxiosResponse>(),
    };
  }

  public request(config: AxiosRequestConfig): AxiosPromise {
    // 中间件
    const chain: PromiseChain<any>[] = [
      {
        resolved: dispatchRequest,
        rejected: undefined,
      },
    ];
    // 后添加的先执行 unshift
    this.interceptors.request.forEach((interceptor) => {
      chain.unshift(interceptor);
    });
    // 先添加的限制性 push
    this.interceptors.response.forEach((interceptor) => {
      chain.push(interceptor);
    });
    let promise = Promise.resolve(config);
    while (chain.length) {
      const { resolved, rejected } = chain.shift()!;
      promise = promise.then(resolved, rejected);
    }
    return promise as unknown as AxiosPromise;
  }

  public put(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): AxiosPromise {
    return this._requestMethodWithData("put", url, data, config);
  }

  public patch(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): AxiosPromise {
    return this._requestMethodWithData("patch", url, data, config);
  }

  public post(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): AxiosPromise {
    return this._requestMethodWithData("post", url, data, config);
  }

  public delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithOutData("delete", url, config);
  }

  public options(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithOutData("options", url, config);
  }

  public get(url: string, config: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithOutData("get", url, config);
  }

  private _requestMethodWithOutData(
    method: Method,
    url: string,
    config?: AxiosRequestConfig
  ): AxiosPromise {
    return this.request({ ...config, method, url });
  }

  private _requestMethodWithData(
    method: Method,
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): AxiosPromise {
    return this.request({ ...config, method, data, url });
  }
}
